﻿using System;
using System.Windows.Forms;
using OpenGL.Enums;
using OpenGL.Exceptions;

namespace OpenGL
{
    public class OpenGLControl : UserControl
    {
        private IntPtr deviceContext = IntPtr.Zero;    // контекст Windows
        private IntPtr renderingContext = IntPtr.Zero;    // контекст передачи OpenGL
        private bool isContextDisposed = false;
        private bool isContextInited = false;
        private int ticks = 0, lastTicks = 0;

        public OpenGLControl() { }

        protected override void Dispose(bool disposing)
        {
            DisposeContext();
            base.Dispose(disposing);
        }
        ~OpenGLControl()
        {
            DisposeContext();
        }
        protected void DisposeContext()
        {
            if (isContextDisposed)
            {
                return;
            }
            isContextDisposed = true;

            GL.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
            if (renderingContext != IntPtr.Zero)
            {
                GL.wglDeleteContext(renderingContext);
            }
            renderingContext = IntPtr.Zero;
        }

        protected override void OnCreateControl()
        {
            base.OnCreateControl();

            this.SetStyle(ControlStyles.UserPaint, false);
        }

        public void InitOpenGL()
        {
            lastTicks = Environment.TickCount;

            if (OpenGLInit() == false)
            {
                throw new OpenGLInitializationException();
            }
        }

        protected sealed override void OnPaint(PaintEventArgs eventArgs)
        {
            // Замещаем методы OnPaint и OnSizeChanged класса Control, 
            // соответствеющие обработчикам WM_PAINT и WM_SIZE
            throw new InvalidOperationException();
        }
        protected sealed override void OnPaintBackground(PaintEventArgs pevent)
        {
            //Окно OpenGL не должно позволять Windows стирать свой фон
            throw new InvalidOperationException();
        }

        protected void SwapBuffers()
        {
            GL.SwapBuffers(deviceContext);
        }
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);

            if (!isContextInited)
            {
                return;
            }

            PrepareScene();
        }
        protected unsafe bool OpenGLInit()
        {
            try
            {
                // GL.Init();
                // Retrieve a handle to a display device context for the client area of the specified window
                IntPtr hdc = GL.GetDC(this.Handle);

                // Create a pixel format
                var pfd = SetPixelFormatDescriptor();
                pfd.nSize = (ushort)sizeof(PixelFormatDescriptor);

                // Match an appropriate pixel format 
                int iPixelformat;
                if ((iPixelformat = GL.ChoosePixelFormat(hdc, &pfd)) == 0)
                    return false;

                // Sets the pixel format
                if (GL.SetPixelFormat(hdc, iPixelformat, &pfd) == 0)
                    return false;

                // Create a new OpenGL rendering contex
                renderingContext = GL.wglCreateContext(hdc);
                deviceContext = hdc;

                // Make the OpenGL rendering context the current rendering context
                int rv = GL.wglMakeCurrent(deviceContext, renderingContext);
            }
            catch
            {
                DisposeContext();
                return false;
            }

            isContextInited = true;

            GL.InitExtensions();

            PrepareScene();
            return true;
        }

        protected virtual PixelFormatDescriptor SetPixelFormatDescriptor()
        {
            PixelFormatDescriptor pfd;
            //pfd.nSize = (ushort)sizeof(PixelFormatDescriptor);
            pfd.nSize = (ushort)0;
            pfd.nVersion = 1;
            pfd.dwFlags = (uint)(DrawWindowFlag.PFD_DRAW_TO_WINDOW | DrawWindowFlag.PFD_SUPPORT_OPENGL | DrawWindowFlag.PFD_DOUBLEBUFFER);
            pfd.iPixelType = (byte)PixelFormat.PFD_TYPE_RGBA;
            pfd.cColorBits = 24;
            pfd.cRedBits = 0;
            pfd.cRedShift = 0;
            pfd.cGreenBits = 0;
            pfd.cGreenShift = 0;
            pfd.cBlueBits = 0;
            pfd.cBlueShift = 0;
            pfd.cAlphaBits = 0;
            pfd.cAlphaShift = 0;
            pfd.cAccumBits = 0;
            pfd.cAccumRedBits = 0;
            pfd.cAccumGreenBits = 0;
            pfd.cAccumBlueBits = 0;
            pfd.cAccumAlphaBits = 0;
            pfd.cDepthBits = 16;
            pfd.cStencilBits = 0;
            pfd.cAuxBuffers = 0;
            pfd.iLayerType = (sbyte)LayerType.PFD_MAIN_PLANE;
            pfd.bReserved = 0;
            pfd.dwLayerMask = 0;
            pfd.dwVisibleMask = 0;
            pfd.dwDamageMask = 0;
            return pfd;
        }
        protected virtual void PrepareScene()
        {
            GL.Viewport(0, 0, this.Width, this.Height);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();

            GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            GL.Ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
        }
        public void Render()
        {
            ticks = Environment.TickCount;
            Scene(ticks - lastTicks);
            lastTicks = ticks;

            SwapBuffers();
        }
        public virtual void Scene(int ticks)
        {       
            GL.Clear(ClearTarget.GL_COLOR_BUFFER_BIT | ClearTarget.GL_DEPTH_BUFFER_BIT);
            GL.Color(0.0f, 0.0f, 1.0f);
          
            GL.Begin(BeginMode.GL_POLYGON);
            GL.Vertex(-0.5f, -0.5f);
            GL.Vertex(-0.5f, 0.5f);
            GL.Vertex(0.5f, 0.5f);
            GL.Vertex(0.5f, -0.5f);
            GL.End();
            GL.Flush();
        }
    }
}